package com.example.ssrbac.service.impl;

import com.example.ssrbac.dto.UserDTO;
import com.example.ssrbac.entity.Role;
import com.example.ssrbac.entity.User;
import com.example.ssrbac.repository.RoleRepository;
import com.example.ssrbac.repository.UserRepository;
import com.example.ssrbac.security.JwtTokenUtil;
import com.example.ssrbac.service.UserService;
import com.example.ssrbac.service.WebSocketService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;
import java.util.concurrent.TimeUnit;

@Service
public class UserServiceImpl implements UserService {
    private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    private WebSocketService webSocketService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.debug("Attempting to load user by username: {}", username);
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> {
                    logger.error("User not found: {}", username);
                    return new UsernameNotFoundException("User not found: " + username);
                });

        Set<SimpleGrantedAuthority> authorities = user.getRoles().stream()
                .flatMap(role -> role.getPermissions().stream())
                .map(permission -> new SimpleGrantedAuthority(permission.getName()))
                .collect(Collectors.toSet());

        logger.debug("User {} loaded successfully with {} authorities", username, authorities.size());
        return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                user.isEnabled(),
                true,
                true,
                true,
                authorities
        );
    }

    @Override
    @Transactional
    public UserDTO createUser(UserDTO userDTO) {
        logger.info("Creating new user with username: {}", userDTO.getUsername());
        if (userRepository.existsByUsername(userDTO.getUsername())) {
            logger.error("Username already exists: {}", userDTO.getUsername());
            throw new RuntimeException("Username already exists");
        }

        User user = new User();
        user.setUsername(userDTO.getUsername());
        user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        user.setEmail(userDTO.getEmail());
        user.setEnabled(true);

        if (userDTO.getRoleIds() != null && !userDTO.getRoleIds().isEmpty()) {
            logger.debug("Assigning {} roles to user {}", userDTO.getRoleIds().size(), userDTO.getUsername());
            Set<Role> roles = userDTO.getRoleIds().stream()
                    .map(roleId -> roleRepository.findById(roleId)
                            .orElseThrow(() -> {
                                logger.error("Role not found: {}", roleId);
                                return new RuntimeException("Role not found: " + roleId);
                            }))
                    .collect(Collectors.toSet());
            user.setRoles(roles);
        }

        User savedUser = userRepository.save(user);
        logger.info("User created successfully with ID: {}", savedUser.getId());
        return convertToDTO(savedUser);
    }

    @Override
    @Transactional
    public UserDTO updateUser(Long id, UserDTO userDTO) {
        logger.info("Updating user with ID: {}", id);
        User user = userRepository.findById(id)
                .orElseThrow(() -> {
                    logger.error("User not found: {}", id);
                    return new RuntimeException("User not found: " + id);
                });

        if (!user.getUsername().equals(userDTO.getUsername()) &&
                userRepository.existsByUsername(userDTO.getUsername())) {
            logger.error("Username already exists: {}", userDTO.getUsername());
            throw new RuntimeException("Username already exists");
        }

        user.setUsername(userDTO.getUsername());
        if (userDTO.getPassword() != null && !userDTO.getPassword().isEmpty()) {
            logger.debug("Updating password for user: {}", id);
            user.setPassword(passwordEncoder.encode(userDTO.getPassword()));
        }
        user.setEmail(userDTO.getEmail());
        user.setEnabled(userDTO.isEnabled());

        if (userDTO.getRoleIds() != null) {
            logger.debug("Updating roles for user: {}", id);
            Set<Role> roles = userDTO.getRoleIds().stream()
                    .map(roleId -> roleRepository.findById(roleId)
                            .orElseThrow(() -> {
                                logger.error("Role not found: {}", roleId);
                                return new RuntimeException("Role not found: " + roleId);
                            }))
                    .collect(Collectors.toSet());
            user.setRoles(roles);
        }

        User updatedUser = userRepository.save(user);
        logger.info("User updated successfully: {}", id);
        return convertToDTO(updatedUser);
    }

    @Override
    @Transactional
    public void deleteUser(Long id) {
        logger.info("Deleting user with ID: {}", id);
        userRepository.deleteById(id);
        logger.info("User deleted successfully: {}", id);
    }

    @Override
    public UserDTO getUserById(Long id) {
        logger.debug("Fetching user by ID: {}", id);
        User user = userRepository.findById(id)
                .orElseThrow(() -> {
                    logger.error("User not found: {}", id);
                    return new RuntimeException("User not found: " + id);
                });
        return convertToDTO(user);
    }

    @Override
    public Page<UserDTO> getAllUsers(Pageable pageable) {
        logger.debug("Fetching all users with pagination: {}", pageable);
        return userRepository.findAll(pageable)
                .map(this::convertToDTO);
    }

    @Override
    @Transactional
    public UserDTO assignRoles(Long userId, Set<Long> roleIds) {
        logger.info("Assigning {} roles to user: {}", roleIds.size(), userId);
        User user = userRepository.findById(userId)
                .orElseThrow(() -> {
                    logger.error("User not found: {}", userId);
                    return new RuntimeException("User not found: " + userId);
                });

        Set<Role> roles = roleIds.stream()
                .map(roleId -> roleRepository.findById(roleId)
                        .orElseThrow(() -> {
                            logger.error("Role not found: {}", roleId);
                            return new RuntimeException("Role not found: " + roleId);
                        }))
                .collect(Collectors.toSet());

        user.setRoles(roles);
        User updatedUser = userRepository.save(user);
        logger.info("Roles assigned successfully to user: {}", userId);
        return convertToDTO(updatedUser);
    }

    @Override
    public User findByUsername(String username) {
        logger.debug("Finding user by username: {}", username);
        return userRepository.findByUsername(username)
                .orElseThrow(() -> {
                    logger.error("User not found: {}", username);
                    return new RuntimeException("User not found: " + username);
                });
    }

    @Override
    public User findById(Long id) {
        logger.debug("Finding user by ID: {}", id);
        return userRepository.findById(id)
                .orElseThrow(() -> {
                    logger.error("User not found: {}", id);
                    return new RuntimeException("User not found: " + id);
                });
    }

    @Override
    public List<User> findAll() {
        logger.debug("Fetching all users");
        return userRepository.findAll();
    }

    @Override
    public User save(User user) {
        logger.info("Saving user: {}", user.getUsername());
        return userRepository.save(user);
    }

    @Override
    public void deleteById(Long id) {
        logger.info("Deleting user by ID: {}", id);
        userRepository.deleteById(id);
    }

    @Override
    public void logout(String token) {
        logger.info("Processing logout for token");
        jwtTokenUtil.invalidateToken(token);
        logger.info("Token invalidated successfully");
    }

    @Override
    public void forceLogout(String username) {
        logger.info("Processing force logout for user: {}", username);
        User user = findByUsername(username);
        
        // 获取用户的当前token
        String currentToken = (String) redisTemplate.opsForValue().get("user:" + username + ":token");
        if (currentToken != null) {
            // 将token加入黑名单
            redisTemplate.opsForValue().set("blacklist:" + currentToken, "1", 24, TimeUnit.HOURS);
            // 删除用户的token
            redisTemplate.delete("user:" + username + ":token");
        }
        
        // 发送强制下线消息
        webSocketService.sendForceLogoutMessage(username);
        
        logger.info("Force logout completed for user: {}", username);
    }

    private UserDTO convertToDTO(User user) {
        UserDTO dto = new UserDTO();
        dto.setId(user.getId());
        dto.setUsername(user.getUsername());
        dto.setEmail(user.getEmail());
        dto.setEnabled(user.isEnabled());
        dto.setRoleIds(user.getRoles().stream()
                .map(Role::getId)
                .collect(Collectors.toSet()));
        return dto;
    }
} 